/*
 * 为了将字符显示到显示器上，通常需要一种硬件的支持，这种硬件就是显卡。
 * 显卡的主要作用是控制显示器的显示模式和状态，并将内容显示到显示器上。
 * 显卡控制显示器的最小单位是像素，一个像素对应显示器上的一个点。
 * 为了控制像素的显示，在显卡上通常有一个按字节访问的存储器——显示存储器（Video RAM，简称显存）。
 * 将需要显示的内容预先写入显存中，显卡会周期性地把内容从显存显示到显示器上。
 *
 * 显卡支持的显示模式通常由两类：字符模式（Character Mode）和图形模式（Graphics Mode）。
 * MaQueOS使用的显示模式为图形模式。在MaQueOS中，显示参数被定义为宏。
 */

#include <xtos.h>
#include <stdint.h>
#include <stddef.h>

#define NR_PIX_X 1280 /* 显示器横轴分辨率 */
#define NR_PIX_Y 800 /* 显示器纵轴分辨率 */
#define CHAR_HEIGHT 18 /* 字模高 */
#define CHAR_WIDTH 9 /* 字模宽 */
#define INPUTBOX_H 3  /* 输入法框高几个字符 虽然输入法框只需要2行字，可我们需要额外的高度以绘制框体 */
#define INPUTBOX_DELTA 4 /* 输出区域与输入框的距离 */
#define NR_CHAR_X (NR_PIX_X / CHAR_WIDTH) /* 每行可显示字符 */
#define NR_CHAR_Y (NR_PIX_Y / CHAR_HEIGHT - INPUTBOX_H) /* 每列可显示字符 */
#define NR_BYTE_PIX 4 /* 像素字节 */
#define VRAM_BASE (0x40000000UL | DMW_MASK) /* 显存起始地址 */
#define L7A_I8042_DATA (0x1fe00060UL | DMW_MASK)   /* ？ */



#define BUFFER_SIZE PAGE_SIZE /* 队列缓冲区大小 */
#define WCHAR_BUFSIZE BUFFER_SIZE / 4    /* 缓冲区一共能装下多少个宽字符 */

/*-------------中文矢量字库--------------*/
/* 这些变量在 chsf.c 中声明 */
extern uint8_t ttu[];
extern int ttunumber;
extern uint8_t zzhi[];
extern uint8_t zzhi32[];
extern int zzhinumber;
extern int zzhinumber32;

/* 这些变量似乎用于以某种格式访问字库内存 */
uint16_t *chss;
uint32_t *chss32;
uint8_t *vec;
int zn;
int zn32;
int vennum;

/* 当前颜色 */
uint8_t blue;
uint8_t green;
uint8_t red;

void rectangle(int x1, int y1, int x2, int y2, int filled); //绘制矩形
void drawvfont(int x, int y, wchar_t z); // 绘制字体
/*-------------中文矢量字库--------------*/

/* 宽字符队列 */
struct w_queue {
	int count, head, tail;
	struct process *wait;
	wchar_t *buffer;
}readw_queue;/* 存储输入字符的队列 */

/* 字符队列 */
struct w_queue {
	int count, head, tail;
	struct process *wait;
	char *buffer;
}input_method_queue;/* 存储直接输入字符的队列 */


extern char fonts[];/* 原生的字库，定义于fonts.c （弃）*/
int x, y;/* 光标所在位置 */
int sum_char_x[NR_CHAR_Y]; /* 用于保存每行有多少个字符 */
char digits_map[] = "0123456789abcdef"; /* ？ */
char keys_map[] = { /* 键盘映射关系 */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '`', 0,
	0, 0, 0, 0, 0, 'q', '1', 0, 0, 0, 'z', 's', 'a', 'w', '2', 0,
	0, 'c', 'x', 'd', 'e', '4', '3', 0, 0, 32, 'v', 'f', 't', 'r', '5', 0,
	0, 'n', 'b', 'h', 'g', 'y', '6', 0, 0, 0, 'm', 'j', 'u', '7', '8', 0,
	0, ',', 'k', 'i', 'o', '0', '9', 0, 0, '.', '/', 'l', ';', 'p', '-', 0,
	0, 0, '\'', 0, '[', '=', 0, 0, 0, 0, 13, ']', 0, '\\', 0, 0,
	0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, '`', 0};

/* 20250403
 * 此函数用于代替write_char()，
 * 其特点是接受 wchat_t 宽字符串
 *
 * 此函数的作者是qing
 */
void writew_char(wchar_t utf, int xx, int yy) {
	drawvfont((xx*CHAR_WIDTH), (yy*CHAR_HEIGHT), (uint16_t)utf);
}

/*
 * 字符擦除函数erase_char用于擦除显示器上给定坐标（xx,yy）处的字符。
 * 所谓擦除，就是将像素绘制成背景色（黑色）
 *
 * 此函数由周庆国教授团队编写，并由qing改写，以适应两倍宽的汉字
 */
void erase_char(int xx, int yy, int isw) {
	int row, col;
	char *pos;
    /* 获取待擦除字符的起始像素在显存中的起始地址。 */
	pos = (char *)(VRAM_BASE + (yy * CHAR_HEIGHT * NR_PIX_X + xx * CHAR_WIDTH) * NR_BYTE_PIX);
    /* 按行循环遍历待擦除字符的像素 */
	for (row = 0; row < CHAR_HEIGHT; row++) {
        /* 每行按列循环遍历待擦除字符的像素 */
		for (col = 0; col < (isw ? 2 * CHAR_WIDTH : CHAR_WIDTH); col++) {
			*pos++ = 0;
			*pos++ = 0;
			*pos++ = 0;
			*pos++ = 0;
		}
        /* 每行遍历结束后，重新获取字符下一行像素的起始像素在显存中的起始地址。 */
		pos += (NR_PIX_X - CHAR_WIDTH) * NR_BYTE_PIX;
	}
}

/*
 * 当显示内容超出显示器最后一行时，需要调用scrup函数进行卷屏处理。
 * 卷屏的主要处理过程是将显示器第1~49行的内容分别复制到第0~48行中，并将第49行的内容擦除。
 *
 * 此函数由周庆国教授团队编写
 */
void scrup() {
	int i;
	char *from, *to;
    /* 获取显示器第0行第0列的起始像素在显存中的地址 */
	to = (char *)VRAM_BASE;
    /* 获取显示器第1行第0列字符的起始像素在显存中的地址。 */
	from = (char *)(VRAM_BASE + (CHAR_HEIGHT * NR_PIX_X * NR_BYTE_PIX));
    /* 将显示器第1~49行中所有像素在显存中的内容，以字节大小为单位，复制到第0~48行对应的显存中 */
	for (i = 0; i < (NR_PIX_Y - CHAR_HEIGHT - INPUTBOX_H * CHAR_HEIGHT) * NR_PIX_X * NR_BYTE_PIX; i++, to++, from++) {
		*to = *from;
	}
    /* 循环调用erase_char函数，擦除第49行中的160个字符 */
	for (i = 0; i < NR_CHAR_X; i++) {
		erase_char(i, NR_CHAR_Y - 1, 0);
	}
    /* 将显示器第1~49行在数组sum_char中对应项的值分别复制到第0~48行对应的项中 */
	for (i = 0; i < NR_CHAR_Y - 1; i++) {
		sum_char_x[i] = sum_char_x[i + 1];
	}
    /* 将显示器最后一行在数组sum_char_x中的对应项清0。 */
	sum_char_x[i] = 0;
}

/*
 * 对回车换行的处理由函数cr_lf实现。
 *
 * 此代码由周庆国教授团队编写。
 */
void cr_lf() {
	x = 0; // 将列坐标x清零0，实现回车处理。
    /* 进行换行处理 */
	if (y < NR_CHAR_Y - 1) { /* 若当前行不是显示器的最后一行，则通过将行坐标y加1，实现换行处理 */
		y++;
	} else { /* 若当前行是显示器的最后一行，则需要调用scrup函数进行卷屏处理。 */
		scrup();
	}
}

/*
 * 对字符的删除处理由函数del实现。
 *
 * 此函数由周庆国教授团队编写。
 */
void del() {

    /* 此时坐标(x,y)指向光标所在的位置，若光标不位于行首 */
	if (x) {
		x--; /* 对列坐标减1后，坐标(x,y)指向待删除的字符 */
		sum_char_x[y] = x; /* 更新sum_char_x数组 */
	}

    /* 若光标位于行首，且不在首行，表示待删除字符是上一行的最后一个字符 */
	else if (y) {
		sum_char_x[y] = 0; /* 将当前行的字符数清0 */
		y--; /* 将行坐标减1 */
		x = sum_char_x[y]; /* 从sum_char_x数组获取待删除字符的行坐标。此时，坐标(x,y)指向待删除的字符。 */
	}

    /* 调用erase_char函数，删除待删除字符。 */
    erase_char(x, y, 0);
}

/*
 * 为了提供在显示器上显示字符串的接口，MaQueOS实现了一个接口函数printk，该函数的作用是将传递给它的字符串参数显示在显示器上。
 *
 * 在显示器驱动程序中，printk函数用于将指定的字符串显示到显示器上，显示位置由(x,y)坐标指定
 * printk函数的参数为需显示字符串的起始地址。
 *
 * 本函数的作者是周庆国教授团队。
 *
 * this function is old, please use printk_w()
 */
void printk(char *buf) {
	char c;
	int nr = 0;

    /* 通过while循环计算字符串的长度 */
	while (buf[nr] != '\0') { 
		nr++;
	}
    /* 调用erase_char函数擦除光标 */
	erase_char(x, y, 0);
    /* 遍历循环字符串中的所有字符，根据字符的ASCII值进行相应的处理 */
	while (nr--) {
		c = *buf++; // 从字符串中获取待处理字符的ASCII值
		if (c > 31 && c < 127) { // 若该字符为可显示字符，则调用write_char函数，将字符显示在显示器上
			writew_char((wchar_t)c, x, y);
			sum_char_x[y] = x; // 将显示器上当前已显示的字符的个数保存到sum_char_x数组中，每行对应数组中的1项，总共50项
			x++; // 将列数加1
			if (x >= NR_CHAR_X) // 若该字符为当前行的最后一个字符，则调用cr_lf函数进行回车换行处理
				cr_lf();
		} else if (c == 10 || c == 13) { // 若该字符为换行符或回车符，则调用cr_lf函数进行处理
			cr_lf();
		} else if (c == 127) { // 若该字符为删除符，则调用del函数进行处理
			del();
		}
		else { // 若该字符不属于以上情况，则视为不可识别字符，调用panic函数显示作物信息
			panic_w(L"panic: unsurpported char!\n");
		}
	}

    /* 
     * 光标由字符“_”表示，用于指示下一个显示字符在显示器上的位置。
     * 当字符串处理结束后，调用write_char函数在字符串末尾显示光标，并指示下一个显示字符在显示器上的位置。
     */
	writew_char(L'_', x, y);
}

/*
 * 处理宽字符的printk()
 *
 * 本函数的作者是qing。
 */
void printk_w(wchar_t *buf) {
	wchar_t c;
	int nr = 0;

	while (buf[nr] != '\0') {
		nr++;
	}

	erase_char(x, y, 0);
	while (nr--) {
		c = *buf++;
		if (c > 31 && c != 127) {

			writew_char(c, x, y);
			sum_char_x[y] = x;
			x++;

			if (c > 127) { /* 中文字符占2个宽度 */
				x++;
			}

			if (x >= NR_CHAR_X)
				cr_lf();

		} else if (c == 10 || c == 13) {
			cr_lf();
		} else if (c == 127) {
			del();
		}
		else {
			panic("panic: unsurpported char!\n");
		}
	}

	writew_char(L'_', x, y);
}

/*
 * 当系统发生错误需要终止运行时，调用panic函数显示出错信息，并进入死循环。
 */
void panic(char *s) {
	printk(s);
	while (1);
}

void panic_w(wchar_t *s) {
	printk_w(s);
	while(1);
}

void print_debug(char *str, unsigned long val) {
	int i, j;
	char buffer[20];

	printk(str);
	buffer[0] = '0';
	buffer[1] = 'x';

	for (j = 0, i = 17; j < 16; j++, i--) {
		buffer[i] = (digits_map[val & 0xfUL]);
		val >>= 4;
	}

	buffer[18] = '\n';
	buffer[19] = '\0';
	printk(buffer);
}

/* 20250403
 * 此函数用于代替put_queue()函数
 *
 * 此函数的作者是qing
 */
void putw_queue(wchar_t c) {
	if (!c) return;
	if (readw_queue.count >= BUFFER_SIZE/4) {
		return;
	}

	readw_queue.buffer[readw_queue.head] = c;
	readw_queue.head = (readw_queue.head + 1) & (BUFFER_SIZE/4 - 1);
	readw_queue.count++;
	wake_up(&readw_queue.wait);
}

/*
 * 该函数专用于添加到输入法的队列
 */
void put_queue(char c) {
	if (!c) return;
	if (read_queue.count >= BUFFER_SIZE) {
		return;
	}

	read_queue.buffer[read_queue.head] = c;
	read_queue.head = (read_queue.head + 1) & (BUFFER_SIZE - 1);
	read_queue.count++;
	wake_up(&read_queue.wait);
}


/*-------------------------------------------------------------------------*/
/* 输入输出
 *
 * 同时将input和output改为wchar版，因为字节数不一样，将会导致需要用额外的寄存器去存
 *
 * 我们可以保留普通的output并且更名inMethod_output，用于往字母专用的队列中添加元素
 *
 * 然后真正的系统input只返回宽字符队列中的文字
 *
 */

//---------------------------------------
int sys_output(char *buf) {
	printk(buf);
	return 0;
}

int sys_outputw(wchar_t *buf) {
	printk_w(buf);
	return 0;
}

//---------------------------------------

int sys_input(char *buf) {
	if (readw_queue.count == 0) {
                sleep_on(&readw_queue.wait);
	}

	*buf = (char)readw_queue.buffer[readw_queue.tail];
	readw_queue.tail = ((readw_queue.tail) + 1) & (BUFFER_SIZE/4 -1);
	readw_queue.count--;
	return 0;
}

void keyboard_interrupt() {
	unsigned char c;

	c = *(volatile unsigned char *)L7A_I8042_DATA;
	if (c == 0xf0) {
		c = *(volatile unsigned char *)L7A_I8042_DATA;
		return;
	}
	//putw_queue((wchar_t)keys_map[c]);
	put_queue(keys_map[c]); //将字符打入普通队列
}

void con_init() {

    /* 初始化画笔颜色 */
    blue = 0; 
    green = 0;
    red = 255;

	/* 初始化宽字符读取数组  */
	readw_queue.count = 0;
	readw_queue.head = 0;
	readw_queue.tail = 0;
	readw_queue.wait = 0;
	readw_queue.buffer = (wchar_t *)get_page();
    

	/* 初始化输入法读取数组  */
	input_method_queue.count = 0;
	input_method_queue.head = 0;
	input_method_queue.tail = 0;
	input_method_queue.wait = 0;
	input_method_queue.buffer = (char *)get_page();


	x = 0;
	y = 0;
}

/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/* 20250403: 我们这一阶段的工作主要是在操作系统中植入中文交互的机制 */
    

/* 20250403
 * 该函数用于在屏幕上绘制一个白色的点
 *
 * 此函数的作者为qing
 */
void point(int x, int y) {
	char *pos = (char *)(VRAM_BASE + (y * NR_PIX_X + x) * NR_BYTE_PIX);
	*pos++ = blue;
	*pos++ = green;
	*pos++ = red;
	*pos++ = 0;
}

/* 20250403
 * Bresenham 画直线算法
 *
 * 此函数的作者为Deepseek(qing's)
 */
void line(int x0, int y0, int x1, int y1) {
    int dx = x1 > x0 ? x1 - x0 : x0 - x1;
    int dy = y1 > y0 ? y1 - y0 : y0 - y1;
    int sx = x0 < x1 ? 1 : -1;
    int sy = y0 < y1 ? 1 : -1;
    int err = (dx > dy ? dx : -dy) / 2;
    int e2;
    
    while (1) {
        point(x0, y0);
        if (x0 == x1 && y0 == y1) break;

        e2 = err;

        if (e2 > -dx) {
            err -= dy;
            x0 += sx;
        }

        if (e2 < dy) {
            err += dx;
            y0 += sy;
        }
    }
}

/**
 * 绘制矩形（支持填充）
 * @param x1, y1    矩形的一个顶点坐标
 * @param x2, y2    矩形的对角顶点坐标
 * @param filled    是否填充（1填充 / 0不填充）
 *
 * 此函数的作者为Deepseek(qing)。
 */
void rectangle(int x1, int y1, int x2, int y2, int filled) {
    // 确保 x_start <= x_end, y_start <= y_end
    int x_start = (x1 < x2) ? x1 : x2;
    int x_end = (x1 < x2) ? x2 : x1;
    int y_start = (y1 < y2) ? y1 : y2;
    int y_end = (y1 < y2) ? y2 : y1;

    // 绘制填充（逐行画水平线）
    if (filled) {
        for (int y = y_start; y <= y_end; y++) {
            line(x_start, y, x_end, y);
        }
    }
    // 绘制边框（4条边）
    else {
        line(x_start, y_start, x_end, y_start); // 上边
        line(x_end, y_start, x_end, y_end);     // 右边
        line(x_start, y_end, x_end, y_end);     // 下边
        line(x_start, y_start, x_start, y_end); // 左边
    }
}


/* 20250404
 * 在找到字体位置的情况下进行绘制
 * x, y是左上角坐标，vect指向矢量字库数据，width和high分别代表要绘制的图的宽和高
 *
 * 此函数的开发者为陈必红老师
 */
unsigned char *drawvect(int x, int y, unsigned char *vect, int width, int high) {
    if (!vect) return 0;

    int i = 0, j = 1; // 指向第一对数字
    unsigned char ix, iy; // 从数组取出的第一对坐标放在这
    double x1, y1;//, x2, y2; // 绘制直线的实数的坐标

    if (high == 0) high = width; // 调节高宽

    int ii1, jj1, ii2, jj2; // 最后算出的绘制直线的坐标
    int isend = 0; //是否到达曲线尾
    int isallend = 0; // 是否结束整个图像

    do {
        ix = vect[i];
        iy = vect[j];
        isend = ix & 1; // 奇数代表曲线尾
        isallend = iy & 1; // 奇数代表整个图像结束

        if (isallend) {
            i += 2;
            break; // 整个图像结束返回
        }

        if (isend) { // 如果这里曲线结束什么也不画进到下一条曲线
            i += 2;
            j += 2;
            continue;
        }

        ix /= 2;
        iy /= 2;
        x1 = ix;
        y1 = iy;
        x1 = width * x1 / 128.0;
        y1 = high * y1 / 128.0;
        ii1 = (int)(x1 + x);
        jj1 = (int)(y1 + y);

        int k;
        for (k = 0; k < 100000; k++) {
            //x2 = x1;
            //y2 = y1;
            ii2 = ii1;
            jj2 = jj1;
            i += 2;
            j += 2;
            ix = vect[i];
            iy = vect[j];
            isend = ix & 1; //奇数代表曲线尾
            isallend = iy & 1; //奇数代表整个图像结束
            ix /= 2;
            iy /= 2;
            x1 = ix;
            y1 = iy;
            x1 = width * x1 / 128.0;
            y1 = high * y1 / 128.0;
            ii1 = (int)(x1 + x);
            jj1 = (int)(y1 + y);
            line(ii1, jj1, ii2, jj2);

            if (isend || isallend) {
                break;
			}
        }

        i += 2; //指向下一条曲线
        j += 2;

    } while (!isallend);

    return vect + i;
}

/* 20250404
 * 该函数用于以一个点为左上角绘制矢量字体
 * 
 * 此函数的作者为陈必红老师
 */
void drawvfont(int x, int y, wchar_t z) {
    chss = (uint16_t *)zzhi;
    chss32 = (uint32_t *) zzhi32;
    vec = ttu;
    zn = zzhinumber;
    zn32 = zzhinumber32;
    vennum = ttunumber;
    
    int i, j, k, ii;

    j = -1;
    for (i = 0; i < zn32; i++) {
        if (chss32[i] == z) {
            j = i;
            break;
        }
    }

    if (j < 0) return;

    k = 0;
    for (i = 0; i < vennum; i++) {
        if (k == j)
            break;
        for (ii =i; ii <vennum; ii++) {
            if (vec[2*ii + 1] & 1) {
                k++;
                i = ii;
                break;
            }
        }
    }

    if (k < j) return;
    drawvect(x, y, ttu + 2 * i, CHAR_WIDTH * 2, CHAR_HEIGHT);
}
